from django.conf.urls import url
from django.shortcuts import HttpResponse, reverse, render, redirect
from django.utils.safestring import mark_safe
from django import forms
from django.db.models import Q
import functools
from django.http import QueryDict
from stark.utils.pagination import Pagination
from django.db.models.fields.related import ForeignKey, ManyToManyField


class ModelConfigMapping(object):
    def __init__(self, model, config, prev):
        self.model = model
        self.config = config
        self.prev = prev


def get_choice_text(field, head):
    def inner(self, row=None, header=False):
        if header:
            return head

        func_name = "get_%s_display" % field
        return getattr(row, func_name)()

    return inner


class Row(object):
    def __init__(self, data_list, option, query_dict):
        self.data_list = data_list
        self.option = option
        self.query_dict = query_dict

    def __iter__(self):
        yield '<div class="whole">'

        total_query_dict = self.query_dict.copy()
        total_query_dict._mutable = True

        origin_value_list = self.query_dict.getlist(self.option.field)
        if origin_value_list:  # 选中了某一个，全部选项不激活
            total_query_dict.pop(self.option.field)
            yield '<a href="?%s">全部</a>' % total_query_dict.urlencode()
        else:  # 一个都没选，全部选项激活
            yield '<a href="?%s" class="active">全部</a>' % total_query_dict.urlencode()

        yield '</div>'
        yield '<div class="others">'

        origin_value_list = self.query_dict.getlist(self.option.field)
        for item in self.data_list:
            val = self.option.get_value(item)
            text = self.option.get_text(item)

            query_dict = self.query_dict.copy()
            query_dict._mutable = True
            if not self.option.is_multi:  # 单选
                if str(val) in origin_value_list:
                    # 取消选中
                    query_dict.pop(self.option.field)
                    yield '<a href="?%s" class="active">%s</a>' % (query_dict.urlencode(), text)
                else:
                    # 选中
                    query_dict[self.option.field] = val
                    yield '<a href="?%s">%s</a>' % (query_dict.urlencode(), text)
            else:  # 多选
                multi_value_list = query_dict.getlist(self.option.field)  # 当前的url里的搜索条件
                if str(val) in multi_value_list:
                    # 已经选中，把选中的取消掉
                    multi_value_list.remove(str(val))
                    query_dict.setlist(self.option.field, multi_value_list)
                    yield '<a href="?%s" class="active">%s</a>' % (query_dict.urlencode(), text)
                else:
                    # 多选，选中
                    multi_value_list.append(val)
                    query_dict.setlist(self.option.field, multi_value_list)
                    yield '<a href="?%s">%s</a>' % (query_dict.urlencode(), text)

        yield '</div>'


class Option(object):
    def __init__(self, field, condition=None, is_choices=False, text_func=None, value_func=None, is_multi=False):
        self.field = field
        self.is_choices = is_choices
        if not condition:
            condition = {}
        self.condition = condition
        self.text_func = text_func
        self.value_func = value_func
        self.is_multi = is_multi

    def get_queryset(self, _field, model_class, query_dict=None):
        if isinstance(_field, ForeignKey) or isinstance(_field, ManyToManyField):
            row = Row(_field.rel.model.objects.filter(**self.condition), self, query_dict)  # queryset
        else:
            if self.is_choices:
                row = Row(_field.choices, self, query_dict)  # 元祖 (1,xxx)
            else:
                row = Row(model_class.objects.filter(**self.condition), self, query_dict)  # queryset

        return row

    def get_text(self, item):
        if self.text_func:
            return self.text_func(item)

        return str(item)

    def get_value(self, item):
        if self.value_func:
            return self.value_func(item)

        if self.is_choices:
            return item[0]

        return item.pk


# 去重
class DistinctPasswordOption(Option):
    def get_queryset(self, _field, model_class, query_dict=None):
        return Row(model_class.objects.filter(**self.condition).values_list("password").distinct(), self, query_dict)  # queryset


class ChangList(object):
    """封装列表页面需要的所有功能"""

    def __init__(self, config, queryset, search_list, q, page):
        self.config = config
        self.search_list = search_list
        self.q = q
        self.page = page
        self.action_list = [{"text": func.text, "name": func.__name__} for func in config.get_action_list()]
        self.add_btn = config.get_add_btn()
        self.queryset = queryset
        self.list_display = config.get_list_display()
        self.list_filter = config.get_list_filter()

    def gen_list_filter_rows(self):
        for option in self.list_filter:
            _field = self.config.model_class._meta.get_field(option.field)
            row = option.get_queryset(_field, self.config.model_class, self.config.request.GET)
            yield row


class StarkConfig:
    """
    删除URL和视图的对应关系 与 默认配置
    """
    order_by = []

    # 展示的表头标题
    list_display = []

    # model_form_class
    model_form_class = None

    def __init__(self, model_class, site, prev):
        self.model_class = model_class
        self.site = site
        self.request = None
        self.back_condition_key = "_filter"
        self.prev = prev

    def multi_del(self, request):
        """批量删除操作"""
        pk_list = request.POST.getlist("pk")
        self.model_class.objects.filter(pk__in=pk_list).delete()

    multi_del.text = "批量删除"

    def multi_init(self, request):
        print("批量初始化")

    multi_init.text = "批量初始化"

    # 执行的操作列表
    action_list = []

    # 搜索条件列表
    search_list = []

    # 组合搜索
    list_filter = []

    # 可扩展的组合搜索
    def get_list_filter(self):
        val = []
        val.extend(self.list_filter)
        return val

    # 可扩展的搜索操作
    def get_search_list(self):
        return self.search_list

    def get_search_conn(self, request):
        search_list = self.get_search_list()
        q = request.GET.get("q", "")
        conn = Q()
        conn.connector = "OR"
        if q:
            if search_list:
                for item in search_list:
                    conn.children.append(("%s__contains" % item, q))

        return search_list, conn, q

    # 可扩展的执行的操作
    def get_action_list(self):
        val = []
        val.extend(self.action_list)
        return val

    def get_action_dict(self):
        val = {}
        for item in self.action_list:
            val[item.__name__] = item

        return val

    # 定制checkbox
    def display_checkbox(self, row=None, header=False):
        if header:
            # 表头
            return "选择"

        # 表格数据
        return mark_safe("<input type='checkbox' name='pk' value='%s' />" % row.id)

    # 表格中的操作 => 编辑
    def display_edit(self, row=None, header=False):
        if header:
            # 表头
            return "编辑"

        # 表格数据
        return mark_safe('<a href="%s"><i class="fa fa-edit"></i></a>' % self.reverse_edit_url(row))

    # 表格中的操作 => 删除
    def display_del(self, row=None, header=False):
        if header:
            # 表头
            return "删除"

        # 表格数据
        return mark_safe('<a href="%s"><i class="fa fa-trash-o"></i></a>' % self.reverse_del_url(row))

    # 表格中的操作 => 编辑和删除
    def display_edit_del(self, row=None, header=False):
        if header:
            # 表头
            return "操作"

        # 表格数据
        tpl = """
            <a href="%s"><i class="fa fa-edit"></i></a> |
            <a href="%s"><i class="fa fa-trash-o"></i></a>
        """ % (self.reverse_edit_url(row), self.reverse_del_url(row))

        return mark_safe(tpl)

    # 表格数据显示的排序
    def get_order_by(self):
        return self.order_by

    # 根据list_display中的内容显示
    def get_list_display(self):
        val = []
        val.extend(self.list_display)
        val.append(StarkConfig.display_edit)  # 默认显示编辑/删除操作
        val.append(StarkConfig.display_del)  # 默认显示编辑/删除操作
        return val

    # 获取组合搜索的条件
    def get_list_filter_condition(self):
        # 组合搜索筛选
        comb_condition = {}
        for option in self.get_list_filter():
            element = self.request.GET.getlist(option.field)  # getlist("name") getlist("password") getlist("roles")
            if element:
                comb_condition["%s__in" % option.field] = element

        return comb_condition

    # 添加按钮
    def get_add_btn(self):
        return mark_safe('<a href="%s" class="btn btn-primary">添加</a>' % self.reverse_add_url())

    # 获取modelForm class
    def get_model_form_class(self):
        if self.model_form_class:
            return self.model_form_class

        class AddModelForm(forms.ModelForm):
            class Meta:
                model = self.model_class
                fields = "__all__"

        return AddModelForm

    def get_queryset(self):
        return self.model_class.objects.all()

    def save(self, form, modify=False):
        """

        :param form:
        :param modify: True 表示修改，False表示新增
        :return:
        """
        return form.save()

    # 添加视图
    def add_view(self, request):
        """
        所有添加页面都在此视图中处理
        """
        # 获取modelForm class
        AddModelForm = self.get_model_form_class()

        if request.method == "GET":
            form = AddModelForm()
            return render(request, "stark/change.html", {"form": form})

        form = AddModelForm(request.POST)
        if form.is_valid():
            self.save(form)
            return redirect(self.reverse_list_url())

        return render(request, "stark/change.html", {"form": form})

    # 数据列表视图
    def changelist_view(self, request):
        """
        所有获取数据列表的都在此视图中显示
        """

        # ########### search搜索 ################
        search_list, conn, q = self.get_search_conn(request)

        # ########### 添加按钮 ################
        # add_btn = self.get_add_btn()

        # 要操作的功能列表
        # ########### action ################
        # action_list = self.get_action_dict()
        # action_list = [{"text": func.text, "name": func.__name__} for func in action_list]

        # ########### 分页 ################
        current_page = request.GET.get("page")
        all_count = self.model_class.objects.filter(conn).count()
        query_params = request.GET.copy()
        query_params['_mutable'] = True
        page = Pagination(current_page, all_count, request.path_info, query_params, per_page=6)

        list_filter = self.get_list_filter()
        # 组合搜索筛选
        # comb_condition = {}
        # for option in list_filter:
        #     element = request.GET.getlist(option.field)  # getlist("name") getlist("password") getlist("roles")
        #     if element:
        #         comb_condition["%s__in" % option.field] = element
        """
        {
            'name__in': ['程颖'], 
            'password__in': ['123'], 
            'roles__in': ['女教师']
        }
        """
        # ########### 组合搜索 ################
        # row_list_filter = []
        # for option in list_filter:
        #     _field = self.model_class._meta.get_field(option.field)
        #     row = option.get_queryset(_field, self.model_class, request.GET)
        #     row_list_filter.append(row)

        # 查询数据列表
        origin_queryset = self.get_queryset()
        queryset = origin_queryset.filter(conn).filter(**self.get_list_filter_condition()).order_by(*self.get_order_by()).distinct()[
                   page.start: page.end]

        cl = ChangList(self, queryset, search_list, q, page)

        if request.method == 'POST':
            action_name = request.POST.get("action")
            action_dict = self.get_action_dict()
            if action_name not in action_dict:
                return HttpResponse("非法请求")

            # 对应的批量操作是否有返回值
            response = getattr(self, action_name)(request)
            if response:
                return response

        # list_display = self.get_list_display()
        # # 表头数据
        # head_list = []
        # if list_display:
        #     for name_or_func in list_display:
        #         # 定制功能（checkbox, edit, del.....）
        #         if isinstance(name_or_func, FunctionType):
        #             # 表头第一列
        #             verbose_name = name_or_func(self, header=True)
        #         else:
        #             # 获取表头其他列的标题
        #             verbose_name = self.model_class._meta.get_field(name_or_func).verbose_name
        #         head_list.append(verbose_name)
        # else:
        #     head_list.append(self.model_class._meta.model_name)
        #
        # # 表格数据
        # body_list = []
        # for row in queryset:
        #     data_list = []
        #     if not list_display:
        #         # 没有标题就以数据对象为标题数据
        #         data_list.append(row)
        #         body_list.append(data_list)
        #         continue
        #
        #     for name_or_func in list_display:
        #         if isinstance(name_or_func, FunctionType):
        #             # 定制功能（checkbox, edit, del.....）
        #             val = name_or_func(self, row=row)
        #         else:
        #             # 其他列数据
        #             val = getattr(row, name_or_func)
        #         data_list.append(val)
        #     body_list.append(data_list)

        return render(request, "stark/change_list.html", {"cl": cl})

    # 修改数据视图
    def change_change(self, request, pk):
        """所有的编辑页面"""
        obj = self.model_class.objects.filter(pk=pk).first()
        if not obj:
            return HttpResponse("数据不存在")

        ModelForm = self.get_model_form_class()

        if request.method == 'GET':
            form = ModelForm(instance=obj)
            return render(request, 'stark/change.html', {"form": form})

        form = ModelForm(data=request.POST, instance=obj)
        if form.is_valid():
            self.save(form, modify=True)
            return redirect(self.reverse_list_url())

        return render(request, 'stark/change.html', {"form": form})

    # 删除数据视图
    def delete_view(self, request, pk):
        """所有删除页面"""
        if request.method == 'GET':
            return render(request, "stark/delete.html", {"cancel_url": self.reverse_list_url()})

        obj = self.model_class.objects.filter(pk=pk).delete()

        return redirect(self.reverse_list_url())

    def wrapper(self, func):
        @functools.wraps(func)
        def inner(request, *args, **kwargs):
            self.request = request
            return func(request, *args, **kwargs)

        return inner

    # 生成url
    def get_urls(self):
        urlpatterns = [
            url(r'^list/', self.wrapper(self.changelist_view), name=self.get_list_url_name),
            url(r'^add/', self.wrapper(self.add_view), name=self.get_add_url_name),
            url(r'^(?P<pk>\d+)/change/', self.wrapper(self.change_change), name=self.get_change_url_name),
            url(r'^(?P<pk>\d+)/del/', self.wrapper(self.delete_view), name=self.get_del_url_name),
        ]

        extra = self.extra_urls()
        if extra:
            urlpatterns.extend(extra)

        return urlpatterns

    # 返回生成的url
    @property
    def urls(self):
        return self.get_urls()

    # 额外定制添加url
    def extra_urls(self):
        pass

    @property
    def get_list_url_name(self):
        app_label, model_name = self.model_class._meta.app_label, self.model_class._meta.model_name
        if self.prev:
            name = '%s_%s_%s_changelist' % (app_label, model_name, self.prev)
        else:
            name = '%s_%s_changelist' % (app_label, model_name)

        return name

    @property
    def get_add_url_name(self):
        app_label, model_name = self.model_class._meta.app_label, self.model_class._meta.model_name
        if self.prev:
            name = '%s_%s_%s_add' % (app_label, model_name, self.prev)
        else:
            name = '%s_%s_add' % (app_label, model_name)

        return name

    @property
    def get_change_url_name(self):
        app_label, model_name = self.model_class._meta.app_label, self.model_class._meta.model_name
        if self.prev:
            name = '%s_%s_%s_change' % (app_label, model_name, self.prev)
        else:
            name = '%s_%s_change' % (app_label, model_name)

        return name

    @property
    def get_del_url_name(self):
        app_label, model_name = self.model_class._meta.app_label, self.model_class._meta.model_name
        if self.prev:
            name = '%s_%s_%s_del' % (app_label, model_name, self.prev)
        else:
            name = '%s_%s_del' % (app_label, model_name)

        return name

    # 反向解析changelist的url
    def reverse_list_url(self):
        namespace = self.site.namespace
        name = '%s:%s' % (namespace, self.get_list_url_name)
        list_url = reverse(name)

        origin_condition = self.request.GET.get(self.back_condition_key)
        if not origin_condition:
            return list_url

        list_url = "%s?%s" % (list_url, origin_condition)

        return list_url

    # 反向解析add的url
    def reverse_add_url(self):
        namespace = self.site.namespace
        name = '%s:%s' % (namespace, self.get_add_url_name)

        add_url = reverse(name)

        if not self.request.GET:
            return add_url

        params_str = self.request.GET.urlencode()  # q=史杨梅&q=孙悦
        new_query_dict = QueryDict(mutable=True)
        new_query_dict[self.back_condition_key] = params_str  # ?_filter=q=史杨梅&q=孙悦
        add_url = "%s?%s" % (add_url, new_query_dict.urlencode())  # add_url?_filter=q=史杨梅&q=孙悦

        return add_url

    # 反向解析edit的url
    def reverse_edit_url(self, row):
        namespace = self.site.namespace

        name = '%s:%s' % (namespace, self.get_change_url_name)
        edit_url = reverse(name, args=(row.pk,))

        if not self.request.GET:
            return edit_url

        params_str = self.request.GET.urlencode()  # q=史杨梅&q=孙悦
        new_query_dict = QueryDict(mutable=True)
        new_query_dict[self.back_condition_key] = params_str  # ?_filter=q=史杨梅&q=孙悦
        edit_url = "%s?%s" % (edit_url, new_query_dict.urlencode())  # add_url?_filter=q=史杨梅&q=孙悦

        return edit_url

    # 反向解析del的url
    def reverse_del_url(self, row):
        namespace = self.site.namespace

        name = '%s:%s' % (namespace, self.get_del_url_name)
        del_url = reverse(name, args=(row.pk,))

        if not self.request.GET:
            return del_url

        params_str = self.request.GET.urlencode()  # q=史杨梅&q=孙悦
        new_query_dict = QueryDict(mutable=True)
        new_query_dict[self.back_condition_key] = params_str  # ?_filter=q=史杨梅&q=孙悦
        del_url = "%s?%s" % (del_url, new_query_dict.urlencode())  # add_url?_filter=q=史杨梅&q=孙悦

        return del_url


class AdminSite:
    """
    用于保存 数据库类和处理该类的对象 的对应关系 与 路由分发
    """

    def __init__(self):
        self._registry = []
        self.app_name = "stark"
        self.namespace = "stark"

    def register(self, model_class, stark_config=None, prev=None):
        if not stark_config:
            stark_config = StarkConfig
        self._registry.append(ModelConfigMapping(model_class, stark_config(model_class, self, prev), prev))

    def get_urls(self):
        urlpatterns = []

        for item in self._registry:
            """
            k: model_class
            v: stark_config(model_class, self)
            """
            app_label = item.model._meta.app_label
            model_name = item.model._meta.model_name

            if item.prev:
                urlpatterns.append(url(r'^%s/%s/%s/' % (app_label, model_name, item.prev), (item.config.urls, None, None)))
            else:
                urlpatterns.append(url(r'^%s/%s/' % (app_label, model_name), (item.config.urls, None, None)))

        return urlpatterns

    @property
    def urls(self):
        return self.get_urls(), self.app_name, self.namespace


# 单例模式
site = AdminSite()

"""
知识点梳理：
    inclusion_tag
    urlencode()
    _meta.model_name
    _meta.app_label
    深浅拷贝
    QueryDict对象默认不可改 _mutable=True
    生成器
    路由分发，
        - include
        - 元组([], None, None)
    函数和方法的区别
        from types import FunctionType, MethodType
        - 实例化调用 方法
        - 未实例化调用 函数
    Q的作用
        - 构造 or
        conn = Q()
        conn.connector = "OR"
        conn.children.append(("name__contains", "梅"))
        conn.children.append(("title__contains", "梅"))
        
        models.UserInfo.objects.filter(conn)
    
    反射
        list_display 中
        action_list 批量操作中
        - getattr()
        
    反向生成url
        - reverse()
        
    分页
        - 保留原搜索条件
    
    ModelForm 组件
    
    functools.wrappers(func) 用于保留原函数的元信息(函数名、函数注释等信息)
    
    request.GET
        - request.GET.get()
        - request.GET.getlist()
        - request.GET._mutable = True
        - request.GET.copy()
        - request.GET.urlencode()
        
    mark_safe()
    XSS 攻击
    
    单例模式
        单例类的特点是一个类只允许初始化一次，针对某些特殊场景，比如说一个数据引擎访问点，
        多次创建没有意义，可能还会造成资源浪费，因此单例模式某些情况下可以节省系统开销，
        合理使用单例类，有助于构建一个稳定高效的系统。
        
    获取函数名
        - __name__
    
    autodiscover_modules
        class StarkConfig(AppConfig):
            name = 'stark'
        
            def ready(self):
                from django.utils.module_loading import autodiscover_modules
                # 去每个app中寻找stark.py执行
                autodiscover_modules("stark")
    
    装饰器
    
    QueryDict对象
        params = request.GET.copy()
        params._mutable = True
        
        params['k1'] = 'v1'
        params.setlist("k2",[11,22])
"""
